using System;
using System.Collections;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using Gtk;
using MonoDevelop.Core;
using MonoDevelop.Core.AddIns;
using MonoDevelop.Core.Gui;
using MonoDevelop.Components;
using MonoDevelop.Ide.Gui;
using MonoDevelop.Ide.Gui.Dialogs;
using MonoDevelop.ResourceEditor;

namespace MonoDevelop.ResourceEditor.Gui
{
	/// <summary>Defines the properties and methods for the ResourceList Widget.</summary>
	public class ResourceList : Gtk.TreeView
	{
		#region Events
		/// <summary>Event is raised when the content in the List has been changed</summary>
		public event EventHandler ContentChanged;
		#endregion
		
		#region Properties
		/// <summary>Gets or Sets the List Store for the Widget.</summary>
		/// <value>A ListStore containing a list of all the ResourceItems</value>
		public Gtk.ListStore Store
		{
			get { return this.store; }
			set { this.store = value; }
		}
		
		/// <summary>Gets or Sets the the write protection of the resource list.</summary>
		/// <value>True if the list is protected, otherwise false.</value>
		public bool WriteProtected
		{
			get { return writeProtected; }
			set { writeProtected = value; }
		}
		
		/// <summary>Gets or Sets the the list of resource items in the resource list.</summary>
		/// <value>An ArrayList containing the list of all the resource items.</value>
		public ArrayList ResourceItems
		{
			get { return resourceItems; }
			set { resourceItems = value; }
		}
		#endregion
		
		#region Member Variables
		/// <summary>Holds the value for the Store property.</summary>
		private Gtk.ListStore store;
		
		/// <summary>Holds the value for the WriteProtected property.</summary>
		private bool writeProtected = false;
		
		/// <summary>Holds the valeu for the ResourceItems property.</summary>
		private ArrayList resourceItems = new ArrayList();
		
		/// <summary>Holds the valeu for the SelectedItem property.</summary>
		private ResourceItem selectedItem;
		
		/// <summary>Holds the valeu for the SelectedIter property.</summary>
		private Gtk.TreeIter selectedIter;
		#endregion
		
		/// <summary>Initializes a new instance of the ResourceItem class by specifying the name and the value for the resource.</summary>
		public ResourceList()
		{
			// Initialize the list store and array list
			store = new Gtk.ListStore (typeof (string), typeof(string), typeof(string), typeof(ResourceItem), typeof (Gdk.Pixbuf));
			Model = store;
			
			// Name Column
			Gtk.TreeViewColumn nameColumn = new Gtk.TreeViewColumn();
			nameColumn.Title = "Name";
			nameColumn.Resizable = true;
			Gtk.CellRendererText nameCell = new Gtk.CellRendererText();
			nameCell.Editable = true;
			nameCell.Edited += NameCell_Edited;
			nameColumn.PackStart (nameCell, true);
			nameColumn.AddAttribute(nameCell, "text", 0);
			AppendColumn(nameColumn);
			
			// Type Column
			Gtk.TreeViewColumn typeColumn = new Gtk.TreeViewColumn();
			typeColumn.Title = "Type";
			typeColumn.Resizable = true;
			Gtk.CellRendererText typeCell = new Gtk.CellRendererText();
			typeColumn.PackStart (typeCell, true);
			typeColumn.AddAttribute(typeCell, "text", 1);
			AppendColumn(typeColumn);
			
			// Content Column
			Gtk.TreeViewColumn contentColumn = new Gtk.TreeViewColumn();
			contentColumn.Title = "Content";
			contentColumn.Resizable = true;
			Gtk.CellRendererText contentCell = new Gtk.CellRendererText();
			contentColumn.PackStart (contentCell, true);
			contentColumn.AddAttribute(contentCell, "text", 2);
			AppendColumn(contentColumn);
			
			// TreeView Settings
			this.HeadersVisible = true;
			this.HeadersClickable = true;
			this.Reorderable = true;
			this.RulesHint = true;
			//this.FixedHeightMode = true;
			this.ResizeMode = Gtk.ResizeMode.Immediate;
			
			// Events
			this.PopupMenu += new Gtk.PopupMenuHandler (OnPopupMenu);
			this.ButtonReleaseEvent += new Gtk.ButtonReleaseEventHandler (OnButtonReleased);
			this.Selection.Changed += new EventHandler (OnSelectionChanged);
		}
		
		/// <summary>Add items to the TreeView store.</summary>
		/// <param name="item">A ResourceItem containing the value to be added to the store.</param>
		public void AddItem(ResourceItem item)
		{
			store.AppendValues(item.Name, item.ResourceValue.GetType().FullName, item.ToString(), item, null);
		}
		
		/// <summary>Set the SelectedItem and SelectedIter properties when the selection changes</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="e">An EventArgs object that contains the event data.</param>
		void OnSelectionChanged (object o, EventArgs e)
		{
			Gtk.TreeIter iter;
			Gtk.TreeModel model;

			if (this.Selection.GetSelected (out model, out iter))
			{
				selectedItem = (ResourceItem) model.GetValue (iter, 3);
				selectedIter = iter;
			}
		}
		
		/// <summary>Executes any of the Events if the content of the list has been changed.</summary>
		private void OnContentChanged()
		{
			if (this.ContentChanged != null)
			{
				this.ContentChanged(this, new EventArgs());
			}
		}
		
		/// <summary>Set the ResourceItem and ResourceList values when the resource's name cell was edited</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="args">An EditedArgs object that contains the event data.</param>
		private void NameCell_Edited (object sender, Gtk.EditedArgs args)
		{
			Gtk.TreeIter iter;
			store.GetIter(out iter, new Gtk.TreePath(args.Path));
			string oldName = (string)this.store.GetValue(iter, 0);
			
			if (oldName != args.NewText)
			{
				if (this.FindResourceByName(args.NewText) == null)
				{
					// Set the new resource name for the resource item list
					this.FindResourceByName(oldName).Name = args.NewText;
					
					// Set the new resource name in the store list
					store.SetValue(iter, 0, args.NewText);
					ResourceItem resourceItem = (ResourceItem) store.GetValue(iter, 3);
					resourceItem.Name = args.NewText;
					this.OnContentChanged();
				}
				else
				{
					// Alert the user that the new key name already exists
					Services.MessageService.ShowWarning("The key is already defined. Please choose another one.");
				}
			}
		}
		
		/// <summary>Show the Popup Menu when the right mouse button has been clicked</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="e">An EditedArgs object that contains the event data.</param>
		private void OnPopupMenu (object sender, Gtk.PopupMenuArgs e)
		{
			this.ShowPopup();
			
		}
		
		/// <summary>Show the Popup Menu when the right mouse button has been clicked</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="e">An ButtonReleaseEventArgs object that contains the event data.</param>
		private void OnButtonReleased (object sender, Gtk.ButtonReleaseEventArgs e)
		{
			if (e.Event.Button == 3)
			{
				this.ShowPopup ();
			}
		}
		
		/// <summary>Shows the context menu containing the event
		private void ShowPopup()
		{
			// Create the Popup Menu
			Gtk.Menu menu = new Gtk.Menu ();
			
			// Add string entry
			Gtk.MenuItem addString = new Gtk.MenuItem("Add string entry ...");
			addString.Activated += new EventHandler(OnAddString);
			menu.Append(addString);
			
			// Add files as resource items
			Gtk.MenuItem addFiles = new Gtk.MenuItem("Add files ...");
			addFiles.Activated += new EventHandler(OnAddFiles);
			menu.Append(addFiles);
			
			// Save currently selected resource item as a file
			Gtk.MenuItem saveFile = new Gtk.MenuItem("Save As ...");
			saveFile.Sensitive = (selectedItem.ResourceValue is byte[] || selectedItem.ResourceValue is Bitmap || selectedItem.ResourceValue is System.Drawing.Icon);
			saveFile.Activated += new EventHandler(OnFileSaveAs);
			menu.Append(saveFile);
			
			// Delete 
			Gtk.MenuItem deleteItem = new Gtk.MenuItem("Delete");
			deleteItem.Activated += new EventHandler(OnDelete);
			menu.Append(deleteItem);
			
			// Show the context menu
			menu.Popup (null, null, null, 3, Gtk.Global.CurrentEventTime);
			menu.ShowAll ();
		}
		
		/// <summary>Add a new string Resource Item to the list</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="e">An EventArgs object that contains the event data.</param>
		public void OnAddString (object sender, EventArgs e)
		{
			// Add the new item to both the resource item array list and store
			ResourceItem resourceItem = new ResourceItem(this.GetUniqueName(), "");
			this.ResourceItems.Add(resourceItem);
			this.AddItem(resourceItem);
			this.OnContentChanged();
		}
		
		private string GetUniqueName()
		{
			// Get a unique name for the resource
			string resourceName = "NewItem1";
			int itemNumber = 1;
			while (this.FindResourceByName(resourceName) != null)
			{
				itemNumber++;
				resourceName = "NewItem" + itemNumber.ToString();
			}
			return (resourceName);
		}
		
		/// <summary>Add a new string Resource Item to the list</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="e">An EventArgs object that contains the event data.</param>
		public void OnAddFiles (object sender, EventArgs e)
		{
			FileSelector fs = new FileSelector (GettextCatalog.GetString ("Files to Add"));
			fs.SelectMultiple = true;
			
			// Add All Files
			Gtk.FileFilter allFilter = new Gtk.FileFilter ();
			allFilter.Name = GettextCatalog.GetString ("All Files");
			allFilter.AddPattern ("*");
			fs.AddFilter (allFilter);
			
			// Display the File Selector
			int response = fs.Run();
			fs.Hide();
			
			if (response == (int) Gtk.ResponseType.Ok)
			{
				foreach(string filename in fs.Filenames)
				{
					// Get the Resource Name and Value
					string resourceName = (new FileInfo(filename)).Name;
					resourceName = (this.FindResourceByName(resourceName) != null) ? this.GetUniqueName() : resourceName;
					object resourceValue = this.LoadResource(filename);
					
					// Add the new item to both the resource item array list and store
					ResourceItem resourceItem = new ResourceItem(resourceName, resourceValue);
					this.ResourceItems.Add(resourceItem);
					this.AddItem(resourceItem);
					this.OnContentChanged();
				}
			}
			fs.Dispose();
		}
		
		private void OnFileSaveAs(object sender, EventArgs e)
		{
			//this.selectedItem.Name
			FileSelectorDialog fs = new FileSelectorDialog (GettextCatalog.GetString ("Save as..."), Gtk.FileChooserAction.Save);
			fs.CurrentName = this.selectedItem.Name;
			fs.ShowEncodingSelector = false;
			int response = fs.Run ();
			string fileName = fs.Filename;
			fs.Hide ();
			fs.Destroy();
			
			MonoDevelop.Core.Gui.MessageService service = IdeApp.Services.MessageService;
			
			if (response != (int)Gtk.ResponseType.Ok)
				return;
					
			if (File.Exists(fileName) && service.AskQuestion("File exists. Overrite it?") == false)
				return;
			
			// Save to a file
			byte[] buffer = (byte[]) this.selectedItem.ResourceValue;
			FileStream stream = new FileStream(fileName, FileMode.CreateNew);
			stream.Write(buffer, 0, buffer.Length);
			stream.Close();
			
			// Save file as
			service.ShowMessage(GettextCatalog.GetString ("File saved"));	
			
		}
		
		/// <summary>Gets the resource value for the specified file.</summary>
		/// <param name="fileName">A string containing the path of the file.</param>
		/// <returns>An object containing the resource value.</returns>
		private object LoadResource(string fileName)
		{
			FileInfo fileInfo = new FileInfo(fileName);
			switch (fileInfo.Extension.ToUpper())
			{
				case ".CUR":
					try { return new System.Windows.Forms.Cursor(fileName); } 
					catch { return null; }
				case ".ICO":
					try { return new System.Drawing.Icon(fileName); }
					catch { return null; }
				default:
					// try to read a bitmap
					try { return new Bitmap(fileName); } 
					catch {}
					
					// try to read a serialized object
					try 
					{
						Stream r = File.Open(fileName, FileMode.Open);
						try 
						{
							BinaryFormatter c = new BinaryFormatter();
							object o = c.Deserialize(r);
							r.Close();
							return o;
						} 
						catch 
						{
							r.Close();
						}
					} 
					catch { }
					
					// try to read a byte array :)
					try 
					{
						FileStream s = new FileStream(fileName, FileMode.Open);
						BinaryReader r = new BinaryReader(s);
						Byte[] d = new Byte[(int) s.Length];
						d = r.ReadBytes((int) s.Length);
						s.Close();
						return d;
					} 
					catch(Exception)
					{
						string message = "Problem loading resource " + fileName;
						Services.MessageService.ShowWarning(message);
					}
				break;
			}
			return null;
		}
		
		/// <summary>Delete the current selected item from the list</summary>
		/// <param name="sender">An object that contains the sender data.</param>
		/// <param name="e">An EventArgs object that contains the event data.</param>
		public void OnDelete (object sender, EventArgs e)
		{
			if (this.ResourceItems.Contains(this.selectedItem))
			{
				this.ResourceItems.Remove(this.selectedItem);
				this.DataBind();
				this.OnContentChanged();
			}
		}
		
		/// <summary>Set the specified resource item's value.</summary>
		/// <param name="resourceName">A string containing the name of the resource item to be changed.</param>
		/// <param name="resourceValue">An object containing the value of the resource.</param> 
		public void SetResourceValue(string resourceName, object resourceValue)
		{
			Gtk.TreeIter iter;
			Gtk.TreeModel model;
			ResourceItem item;
			
			// Set the resource value for the resource item list
			this.FindResourceByName(resourceName).ResourceValue = resourceValue;
			
			// Set the resource value in the store list
			if (this.Selection.GetSelected(out model, out iter))
			{
				item = new ResourceItem(resourceName, resourceValue);
				this.store.SetValue(iter, 3, item);
			}
			
			this.OnContentChanged();
		}
		
		/// <summary>Finds a specific resource item by name.</summary>
		/// <param name="resourceName">A string containing the name of the resource to find.</param>
		public ResourceItem FindResourceByName(string resourceName)
		{
			ResourceItem resourceItem = null;
			for (int index = 0; index < this.ResourceItems.Count; index++)
			{
				ResourceItem item = (ResourceItem) this.ResourceItems[index];
				if (item.Name == resourceName)
				{
					resourceItem = item;
					break;
				}
			}
			return (resourceItem);
		}
		
		/// <summary>Binds the ResourceItems to the Widget.</summary>
		public void DataBind()
		{
			store.Clear();
			foreach (ResourceItem item in resourceItems)
			{
				this.AddItem(item);
			}			
		}
	}
}
